import { Matrix4 } from "../../../core/math/index";
import { Quaternion } from "../../../core/math/Quaternion";
import { Vector2 } from "../../../core/math/Vector2";
import { Vector3 } from "../../../core/math/Vector3";
export function clamp(num, min, max) {
  return Math.min(Math.max(num, min), max);
}

export function resolveAfter2Seconds(func, seconds) {
  return new Promise((resolve) => {
    setTimeout(() => {
      const data = func();
      resolve(data);
    }, seconds);
  });
}
export function toFloat(num, defaultValue = 1) {
  const n = num !== undefined ? num : defaultValue;
  if (Number.isInteger(n)) {
    return `${n}.0`;
  }
  return n;
}

export const TypedArray =
  Int8Array |
  Uint8Array |
  Int16Array |
  Uint16Array |
  Uint32Array |
  Float32Array;

export function newTypedArray(type, buffer, byteOffset, length) {
  const n = gltfEnum[type];
  const data = buffer.slice(byteOffset, byteOffset + length * n);
  switch (type) {
    case 5120:
      return new Int8Array(data);
    case 5121:
      return new Uint8Array(data);
    case 5122:
      return new Int16Array(data);
    case 5123:
      return new Uint16Array(data);
    case 5125:
      return new Uint32Array(data);
    case 5126:
      return new Float32Array(data);
    default:
      throw new Error("invalid component type");
  }
}
// export function newTypedArray(type, buffer, byteOffset, length) {
//   switch (type) {
//     case 5120:
//       return new Int8Array(buffer, byteOffset, length);
//     case 5121:
//       return new Uint8Array(buffer, byteOffset, length);
//     case 5122:
//       return new Int16Array(buffer, byteOffset, length);
//     case 5123:
//       return new Uint16Array(buffer, byteOffset, length);
//     case 5125:
//       return new Uint32Array(buffer, byteOffset, length);
//     case 5126:
//       return new Float32Array(buffer, byteOffset, length);
//     default:
//       throw new Error("invalid component type");
//   }
// }

export function toIndices(array) {
  if (array instanceof Uint16Array || array instanceof Uint32Array) {
    return array;
  }
  let toArray;
  if (array instanceof Float32Array) {
    toArray = new Uint32Array(array.length);
  } else {
    toArray = new Uint32Array(array.length);
  }
  array.forEach((element, index) => {
    toArray[index] = element;
  });
  return toArray;
}

export function joinArray(arrays) {
  let length = 0;
  arrays.forEach((array) => {
    length += array.length;
  });
  const joined = new Float32Array(length);
  length = 0;
  arrays.forEach((array) => {
    joined.set(array, length);
    length += array.length;
  });
  return joined;
}

export function createGPUBuffer(array, usage, device) {
  const buffer = device.createBuffer({
    size: (array.byteLength + 3) & ~3, // eslint-disable-line no-bitwise
    usage,
    mappedAtCreation: true,
  });
  let writeArary;
  if (array instanceof Int8Array) {
    writeArary = new Int8Array(buffer.getMappedRange());
  } else if (array instanceof Uint8Array) {
    writeArary = new Uint8Array(buffer.getMappedRange());
  } else if (array instanceof Int16Array) {
    writeArary = new Int16Array(buffer.getMappedRange());
  } else if (array instanceof Uint16Array) {
    writeArary = new Uint16Array(buffer.getMappedRange());
  } else if (array instanceof Uint32Array) {
    writeArary = new Uint32Array(buffer.getMappedRange());
  } else {
    writeArary = new Float32Array(buffer.getMappedRange());
  }
  writeArary.set(array);
  buffer.unmap();
  return buffer;
}

export function generateNormals(indices, positions) {
  const normals = new Float32Array(positions.length);
  const vertexCount = indices ? indices.length : positions.length;
  for (let i = 0; i < vertexCount; i += 3) {
    const triIndices = [];
    for (let n = 0; n < 3; n += 1) {
      if (indices) {
        triIndices.push(indices[i + n]);
      } else {
        triIndices.push(i + n);
      }
    }
    const triangle = triIndices.map((vertexIndex) => {
      const index = vertexIndex * 3;
      return new Vector3(
        positions[index],
        positions[index + 1],
        positions[index + 2]
      );
    });
    const dv1 = new Vector3().subVectors(triangle[1], triangle[0]);
    const dv2 = new Vector3().subVectors(triangle[2], triangle[0]);
    const normal = new Vector3().crossVectors(dv1.normalize(), dv2.normalize());
    for (let n = 0; n < 3; n += 1) {
      const index = (i + n) * 3;
      for (let t = 0; t < 3; t += 1) {
        normals[index + t] += normal[t];
      }
    }
  }
  return normals;
}
export function calculateNormals(indices, vertices) {
  let normals = new Float32Array(vertices.length); // 初始化法向量数组，大小与vertices相同，初始值设为0
  for (let i = 0; i < indices.length; i += 3) {
    let indexA = indices[i] * 3; // 乘以3是因为每个顶点有3个分量(x,y,z)
    let indexB = indices[i + 1] * 3;
    let indexC = indices[i + 2] * 3;
    let vertexA = [
      vertices[indexA],
      vertices[indexA + 1],
      vertices[indexA + 2],
    ];
    let vertexB = [
      vertices[indexB],
      vertices[indexB + 1],
      vertices[indexB + 2],
    ];
    let vertexC = [
      vertices[indexC],
      vertices[indexC + 1],
      vertices[indexC + 2],
    ];
    let v1 = [
      vertexB[0] - vertexA[0],
      vertexB[1] - vertexA[1],
      vertexB[2] - vertexA[2],
    ];
    let v2 = [
      vertexC[0] - vertexA[0],
      vertexC[1] - vertexA[1],
      vertexC[2] - vertexA[2],
    ];
    let nx = v1[1] * v2[2] - v1[2] * v2[1];
    let ny = v1[2] * v2[0] - v1[0] * v2[2];
    let nz = v1[0] * v2[1] - v1[1] * v2[0];
    let length = Math.sqrt(nx * nx + ny * ny + nz * nz);
    let normal = [nx / length, ny / length, nz / length];
    // 将法向量分配给三个顶点，这里简化处理，实际应用中需要累积共享顶点的法向量
    normals.set(normal, indexA);
    normals.set(normal, indexB);
    normals.set(normal, indexC);
  }
  // 如果需要平滑着色，这里还需要额外步骤来累积共享顶点的法向量并重新归一化
  return normals;
}
export function calculateNormalsFromPositions(positions) {
  // 由于没有 indices，我们将每三个相邻的顶点作为三角形的顶点
  let normals = new Float32Array(positions.length); // 初始化法向量数组
  let numVertices = positions.length / 3;

  for (let i = 0; i < numVertices - 2; i++) {
    let indexA = i * 3; // 当前顶点的索引
    let indexB = (i + 1) * 3; // 下一个顶点的索引
    let indexC = (i + 2) * 3; // 再下一个顶点的索引

    // 获取三个顶点的坐标
    let vertexA = [
      positions[indexA],
      positions[indexA + 1],
      positions[indexA + 2],
    ];
    let vertexB = [
      positions[indexB],
      positions[indexB + 1],
      positions[indexB + 2],
    ];
    let vertexC = [
      positions[indexC],
      positions[indexC + 1],
      positions[indexC + 2],
    ];

    // 计算向量 v1 和 v2
    let v1 = [
      vertexB[0] - vertexA[0],
      vertexB[1] - vertexA[1],
      vertexB[2] - vertexA[2],
    ];
    let v2 = [
      vertexC[0] - vertexA[0],
      vertexC[1] - vertexA[1],
      vertexC[2] - vertexA[2],
    ];

    // 计算法向量
    let nx = v1[1] * v2[2] - v1[2] * v2[1];
    let ny = v1[2] * v2[0] - v1[0] * v2[2];
    let nz = v1[0] * v2[1] - v1[1] * v2[0];

    let length = Math.sqrt(nx * nx + ny * ny + nz * nz);
    let normal = length ? [nx / length, ny / length, nz / length] : [0, 0, 0];

    // 将法向量分配给三个顶点
    normals.set(normal, indexA);
    normals.set(normal, indexB);
    normals.set(normal, indexC);
  }

  // 在这里可以添加平滑化法向量的逻辑
  return normals;
}

function accumulateNormal(vertexNormals, uniqueVertices, index, normal) {
  if (!vertexNormals[index]) {
    vertexNormals[index] = [0, 0, 0];
  }
  vertexNormals[index][0] += normal[0];
  vertexNormals[index][1] += normal[1];
  vertexNormals[index][2] += normal[2];
  uniqueVertices.set(index, (uniqueVertices.get(index) || 0) + 1);
}
// 使用示例保持不变
export function generateTangents(indices, positions, normals, uvs) {
  const tangents = new Float32Array((normals.length / 3) * 4);
  const vertexCount = indices ? indices.length : positions.length;
  for (let i = 0; i < vertexCount; i += 3) {
    const triIndices = [];
    for (let n = 0; n < 3; n += 1) {
      if (indices) {
        triIndices.push(indices[i + n]);
      } else {
        triIndices.push(i + n);
      }
    }
    const pos = triIndices.map((vertexIndex) => {
      const index = vertexIndex * 3;
      return new Vector3(
        positions[index],
        positions[index + 1],
        positions[index + 2]
      );
    });
    const uv = triIndices.map((vertexIndex) => {
      const index = vertexIndex * 2;
      return new Vector2(uvs[index], uvs[index + 1]);
    });
    const dv1 = pos[1].sub(pos[0]);
    const dv2 = pos[2].sub(pos[0]);
    const duv1 = uv[1].sub(uv[0]);
    const duv2 = uv[2].sub(uv[0]);
    const tangent = new Vector3();
    tangent.subVectors(
      dv1.multiplyVectors(duv2[1]),
      dv2.multiplyVectors(duv1[1])
    );
    tangent.multiplyScalar(duv2[1] * duv1[0] - duv1[1] * duv2[0]);
    tangent.normalize();
    for (let n = 0; n < 3; n += 1) {
      const index = (i + n) * 4;
      for (let t = 0; t < 3; t += 1) {
        tangents[index + t] += tangent[t];
      }
      tangents[index + 3] = 1;
    }
  }
  return tangents;
}

export function getTextures(material) {
  const { baseColorTexture, metallicRoughnessTexture } =
    material.pbrMetallicRoughness;
  const { normalTexture, occlusionTexture, emissiveTexture } = material;
  return [
    baseColorTexture,
    metallicRoughnessTexture,
    normalTexture,
    occlusionTexture,
    emissiveTexture,
  ];
}
export function getTexturesObject(material) {
  const { baseColorTexture, metallicRoughnessTexture } =
    material.pbrMetallicRoughness;
  const { normalTexture, occlusionTexture, emissiveTexture } = material;
  return {
    baseColorTexture,
    metallicRoughnessTexture,
    normalTexture,
    occlusionTexture,
    emissiveTexture,
  };
}

function lerp(a, b, x) {
  if (x < a) {
    return 0;
  }
  if (x > b) {
    return 1;
  }
  return (x - a) / (b - a);
}

export function interpQuat(input, o, time, method) {
  let index = 1;
  while (index < input.length - 1 && time >= input[index]) {
    index += 1;
  }
  const t = lerp(input[index - 1], input[index], time);
  if (method === "CUBICSPLINE") {
    const td = input[index] - input[index - 1];
    const t2 = t * t;
    const t3 = t2 * t;
    const i = 12 * index;
    const v0 = new Quaternion(o[i - 8], o[i - 7], o[i - 6], o[i - 5]);
    const b0 = new Quaternion(o[i - 4], o[i - 3], o[i - 2], o[i - 1]);
    const v1 = new Quaternion(o[i + 4], o[i + 5], o[i + 6], o[i + 7]);
    const a1 = new Quaternion(o[i], o[i + 1], o[i + 2], o[i + 3]);
    v0.w = v0.w * (2 * t3 - 3 * t2 + 1);
    v0.x = v0.x * (2 * t3 - 3 * t2 + 1);
    v0.y = v0.y * (2 * t3 - 3 * t2 + 1);
    v0.z = v0.z * (2 * t3 - 3 * t2 + 1);
    b0.w = b0.w * (td * (t3 - 2 * t2 + t));
    b0.x = b0.x * (td * (t3 - 2 * t2 + t));
    b0.y = b0.y * (td * (t3 - 2 * t2 + t));
    b0.z = b0.z * (td * (t3 - 2 * t2 + t));
    v1.w = v1.w * (-2 * t3 + 3 * t2);
    v1.x = v1.x * (-2 * t3 + 3 * t2);
    v1.y = v1.y * (-2 * t3 + 3 * t2);
    v1.z = v1.z * (-2 * t3 + 3 * t2);
    a1.w = v1.w * (td * (t3 - t2));
    a1.x = v1.x * (td * (t3 - t2));
    a1.y = v1.y * (td * (t3 - t2));
    a1.z = v1.z * (td * (t3 - t2));
    const result = new Quaternion(0, 0, 0, 0);
    result.add(v0);
    result.add(b0);
    result.add(v1);
    result.add(a1);
    return result.normalize();
  }
  const q = [];
  for (let n = -1; n < 1; n += 1) {
    const i = 4 * (index + n);
    q.push(new Quaternion(o[i], o[i + 1], o[i + 2], o[i + 3]));
  }
  if (method === "STEP") {
    return t < 1 ? q[0] : q[1];
  }
  return q[0].slerp(q[1], t);
}

export function interpVec3(input, output, time, method) {
  let index = 1;
  while (index < input.length - 1 && time >= input[index]) {
    index += 1;
  }
  const t = lerp(input[index - 1], input[index], time);
  if (method === "CUBICSPLINE") {
    const td = input[index] - input[index - 1];
    const t2 = t * t;
    const t3 = t2 * t;
    const i = 9 * index;
    const v0 = new Vector3(output[i - 6], output[i - 5], output[i - 4]);
    const b0 = new Vector3(output[i - 3], output[i - 2], output[i - 1]);
    const v1 = new Vector3(output[i + 3], output[i + 4], output[i + 5]);
    const a1 = new Vector3(output[i], output[i + 1], output[i + 2]);
    v0.multiplyScalar(2 * t3 - 3 * t2 + 1);
    b0.multiplyScalar(td * (t3 - 2 * t2 + t));
    v1.multiplyScalar(-2 * t3 + 3 * t2);
    a1.multiplyScalar(td * (t3 - t2));
    const result = new Vector3();
    result.add(v0);
    result.add(b0);
    result.add(v1);
    result.add(a1);
    return result;
  }
  const v = [];
  for (let n = -1; n < 1; n += 1) {
    const i = 3 * (index + n);
    v.push(new Vector3(output[i], output[i + 1], output[i + 2]));
  }
  if (method === "STEP") {
    return t < 1 ? v[0] : v[1];
  }
  return v[0].lerp(v[1], t);
}

export const gltfEnum = {
  SCALAR: 1,
  VEC2: 2,
  VEC3: 3,
  VEC4: 4,
  MAT2: 4,
  MAT3: 9,
  MAT4: 16,
  5120: 1,
  5121: 1,
  5122: 2,
  5123: 2,
  5125: 4,
  5126: 4,
  9728: "nearest",
  9729: "linear",
  9984: "linear",
  9985: "linear",
  9986: "linear",
  9987: "linear",
  33071: "clamp-to-edge",
  33648: "mirror-repeat",
  10497: "repeat",
};
export const transitionArray = function (arr, num) {
  let newArr = [];
  const total = Math.ceil(arr.length / num);
  for (let i = 0; i < total; i++) {
    const a = arr.slice(i * num, (i + 1) * num);
    newArr.push(a);
  }
  return newArr;
};
export const BASIS_FORMAT = {
  // 压缩
  // ETC1-2
  cTFETC1_RGB: 0, // Opaque only, returns RGB or alpha data if cDecodeFlagsTranscodeAlphaDataToOpaqueFormats flag is specified
  cTFETC2_RGBA: 1, // Opaque+alpha, ETC2_EAC_A8 block followed by a ETC1 block, alpha channel will be opaque for opaque .basis files
  // BC1-5, BC7 (desktop, some mobile devices)
  cTFBC1_RGB: 2, // Opaque only, no punchthrough alpha support yet, transcodes alpha slice if cDecodeFlagsTranscodeAlphaDataToOpaqueFormats flag is specified
  cTFBC3_RGBA: 3, // Opaque+alpha, BC4 followed by a BC1 block, alpha channel will be opaque for opaque .basis files
  cTFBC4_R: 4, // Red only, alpha slice is transcoded to output if cDecodeFlagsTranscodeAlphaDataToOpaqueFormats flag is specified
  cTFBC5_RG: 5, // XY: Two BC4 blocks, X=R and Y=Alpha, .basis file should have alpha data (if not Y will be all 255's)
  cTFBC7_RGBA: 6, // RGB or RGBA, mode 5 for ETC1S, modes (1,2,3,5,6,7) for UASTC
  // PVRTC1 4bpp (mobile, PowerVR devices)
  cTFPVRTC1_4_RGB: 8, // Opaque only, RGB or alpha if cDecodeFlagsTranscodeAlphaDataToOpaqueFormats flag is specified, nearly lowest quality of any texture format.
  cTFPVRTC1_4_RGBA: 9, // Opaque+alpha, most useful for simple opacity maps. If .basis file doesn't have alpha cTFPVRTC1_4_RGB will be used instead. Lowest quality of any supported texture format.
  // ASTC (mobile, Intel devices, hopefully all desktop GPU's one day)
  cTFASTC_4x4_RGBA: 10, // Opaque+alpha, ASTC 4x4, alpha channel will be opaque for opaque .basis files. Transcoder uses RGB/RGBA/L/LA modes, void extent, and up to two ([0,47] and [0,255]) endpoint precisions.
  // 未压缩
  cTFRGBA32: 13, // 32bpp RGBA image stored in raster (not block) order in memory, R is first byte, A is last byte.
  cTFRGB565: 14, // 166pp RGB image stored in raster (not block) order in memory, R at bit position 11
  cTFBGR565: 15, // 16bpp RGB image stored in raster (not block) order in memory, R at bit position 0
  cTFRGBA4444: 16, // 16bpp RGBA image stored in raster (not block) order in memory, R at bit position 12, A at bit position 0
  cTFTotalTextureFormats: 22,
};
/* eslint-enable */

export const WTT_FORMAT_MAP = {};
// 压缩格式
WTT_FORMAT_MAP[BASIS_FORMAT.cTFBC1_RGB] = {
  format: "bc1-rgb-unorm",
  extention: "s3tc",
};
WTT_FORMAT_MAP[BASIS_FORMAT.cTFBC3_RGBA] = {
  format: "bc3-rgba-unorm",
  extention: "s3tc",
};
WTT_FORMAT_MAP[BASIS_FORMAT.cTFBC7_RGBA] = {
  format: "bc7-rgba-unorm",
  extention: "bptc",
};
WTT_FORMAT_MAP[BASIS_FORMAT.cTFETC1_RGB] = {
  format: "etc1-rgb-unorm",
  extention: "etc2",
};
WTT_FORMAT_MAP[BASIS_FORMAT.cTFETC2_RGBA] = {
  format: "etc2-rgba8unorm",
  extention: "etc2",
};
WTT_FORMAT_MAP[BASIS_FORMAT.cTFASTC_4x4_RGBA] = {
  format: "astc-4x4-rgba-unorm",
  extention: "astc",
};
WTT_FORMAT_MAP[BASIS_FORMAT.cTFPVRTC1_4_RGB] = {
  format: "pvrtc1-4bpp-rgb-unorm",
  extention: "pvrtc",
};
WTT_FORMAT_MAP[BASIS_FORMAT.cTFPVRTC1_4_RGBA] = {
  format: "pvrtc1-4bpp-rgba-unorm",
  extention: "pvrtc",
};
//未压缩格式
WTT_FORMAT_MAP[BASIS_FORMAT.cTFRGBA32] = {
  format: "rgba8unorm",
  uncompressed: true,
  extention: "none",
};
WTT_FORMAT_MAP[BASIS_FORMAT.cTFRGB565] = {
  format: "rgb565unorm",
  uncompressed: true,
  extention: "none",
};
WTT_FORMAT_MAP[BASIS_FORMAT.cTFRGBA4444] = {
  format: "rgba4unorm",
  uncompressed: true,
  extention: "none",
};

export function getNormalizedComponentScale(constructor) {
  switch (constructor) {
    case Int8Array:
      return 1 / 127;
    case Uint8Array:
      return 1 / 255;
    case Int16Array:
      return 1 / 32767;
    case Uint16Array:
      return 1 / 65535;
    default:
      throw new Error(" Unsupported normalized accessor component type.");
  }
}
export function computeJoints(
  inverseBindMatrix,
  joints,
  parentWorldMat,
  entities
) {
  let bufferData = new Float32Array(inverseBindMatrix.length);
  const inverseWorldTransform = parentWorldMat.clone().invert();
  for (let index = 0; index < joints.length; index++) {
    const joint = joints[index];
    const entitys = entities;
    let node;
    entitys.forEach((item) => {
      if (item.nodeId === joint) {
        node = item;
      }
    });
    if (node) {
      const nodeWorldMat = node.trans.matrixWorld;
      const data = inverseBindMatrix.slice(index * 16, (index + 1) * 16);
      const ibm = new Matrix4().fromArray(data);
      let jointMatrix = nodeWorldMat.clone();
      const comb = jointMatrix.multiply(ibm);
      // const mat = inverseWorldTransform.multiply(comb);
      bufferData.set(comb.elements, index * 16);
    }
  }
  return bufferData;
}
